package com.github.f4b6a3.uuid.uniq;

import java.util.HashSet;
import java.util.UUID;

import com.github.f4b6a3.uuid.TestSuite;
import com.github.f4b6a3.uuid.factory.UuidFactory;
import com.github.f4b6a3.uuid.factory.standard.TimeBasedFactory;
import com.github.f4b6a3.uuid.util.UuidUtil;

/**
 * This test runs many threads that request time-based UUIDs in a loop.
 * 
 * This is is not included in the {@link TestSuite} because it takes a long time
 * to finish.
 * 
 * The theoretical maximum amount of UUIDs that can be generated at the same
 * millisecond by a single generator without duplicated UUIDs is about 163
 * million UUIDs. It's calculated by the product of the clock sequence range and
 * the count of 100 nanosecond intervals per millisecond, that is:
 * 
 * 16,384 * 10,000 = 163,840,000.
 * 
 * My personal PC don't have enough memory and processor to test the generation
 * of 163 million UUIDs. So the amount of UUIDs generated by this test is not
 * too high.
 *
 * Each UUID is reduced to a `long` value to use less memory. The timestamp high
 * bits and the node identifier bits are ignored since they don't change.
 * 
 * An error message is logged if a UUID value is generated more than once.
 * 
 */
public class UniquenessTest1 {

	UuidFactory factory;
	private int threadCount; // Number of threads to run
	private int requestCount; // Number of requests for thread

	private int finished = 0;

	private static UUID[][] values;

	public UniquenessTest1(UuidFactory factory2, int threadCount, int requestCount) {
		this.factory = factory2;
		this.threadCount = threadCount;
		this.requestCount = requestCount;
		this.init(threadCount, requestCount);
	}

	private void init(int threadCount, int requestCount) {
		values = new UUID[threadCount][requestCount];
	}

	public void start() {

		TestThread[] threads = new TestThread[this.threadCount];

		// Start the threads
		for (int i = 0; i < this.threadCount; i++) {
			threads[i] = new TestThread(i, factory, requestCount);
			threads[i].start();
		}

		// Wait all the threads to finish
		for (TestThread thread : threads) {
			try {
				thread.join();
				int progress = (++finished * 100 / threadCount);
				System.out.println(String.format("Finished thread %s/%s (%s%%)", finished, threadCount, progress));
			} catch (InterruptedException e) {
				Thread.currentThread().interrupt();
			}
		}

		findDuplicates();
	}

	private void findDuplicates() {
		System.out.println();
		HashSet<UUID> s = new HashSet<>();
		HashSet<Integer> cs = new HashSet<>();
		int dup = 0;
		for (int i = 0; i < values.length; i++) {
			for (int j = 0; j < values[0].length; j++) {
				if (!s.add(values[i][j])) {
					dup++;
				}
				cs.add(UuidUtil.setVersion(values[i][j], 1).clockSequence());
			}
			int progress = (((i + 1) * 100) / values.length);
			System.out.println(String.format("Looking for duplicates (%s%%)", progress));
		}

		System.out.println();

		System.out.println(String.format("Clock sequences used: %s", cs.size()));

		String msg1 = String.format("Duplicates found: %s (%s%%)", dup, ((dup * 100) / (threadCount * requestCount)));
		if (dup > 0) {
			System.err.println(msg1 + " [fail]");
		} else {
			System.out.println(msg1 + " [ok]");
		}

	}

	public class TestThread extends Thread {

		public int id;
		public UuidFactory factory;
		public int requests;

		public TestThread(int id, UuidFactory factory, int requests) {
			this.id = id;
			this.factory = factory;
			this.requests = requests;

			if (this.factory == null) {
				// DEDICATED generator that creates time-based UUIDs (v1),
				// that uses a hash instead of a random node identifier,
				// and that uses a fixed millisecond to simulate a loop faster than the clock
				this.factory = newFactory();
			}
		}

		@Override
		public void run() {

			for (int i = 0; i < requests; i++) {
				values[id][i] = factory.create();
			}
		}
	}

	public static void execute(UuidFactory factory, int threads, int requests) {
		UniquenessTest1 test = new UniquenessTest1(factory, threads, requests);
		test.start();
	}

	private static UuidFactory newFactory() {
		// a new generator that creates time-based UUIDs (v1),
		// that uses a hash instead of a random node identifier,
		// and that uses a fixed millisecond to simulate a loop faster than the clock
		return new TimeBasedFactory.Builder().withHashNodeId().build();
	}

	public static void main(String[] args) {

		System.out.println("-----------------------------------------------------");
		System.out.println("SHARED generator for all threads                     ");
		System.out.println("-----------------------------------------------------");

		// SHARED generator for all threads
		UuidFactory factory = newFactory();
		int threadCount = 8; // Number of threads to run
		int requestCount = 1_000_000; // Number of requests for thread
		execute(factory, threadCount, requestCount);

		System.out.println();
		System.out.println("------------------------------------------------------");
		System.out.println("DEDICATED generators for each thread                  ");
		System.out.println("------------------------------------------------------");

		// Dedicated generators for each thread
		factory = null;
		threadCount = 8; // Number of threads to run
		requestCount = 1_000_000; // Number of requests for thread
		execute(factory, threadCount, requestCount);

	}
}